import multiprocessing
import time

import pyvisa
import pyvisa as visa
import logging

from PyQt5.QtCore import QObject, pyqtSignal
from pyvisa import Resource


VISA_TO = 100000  # 超时
instrument_logger = logging.getLogger('instrument')

class InstrumentConnectionFailed(Exception): pass


class PVisa(QObject):
    scpi_emitted = pyqtSignal(str)
    received_answer = pyqtSignal(str)

    def __init__(self, parent=None):
        super().__init__(parent)
        # 设备地址
        self._address = ""
        """
            Resource Manager： 
            重复创建，也只会返回唯一实例。
            不会重复创建多个ResourceManager实例。
            它会复用已有的ResourceManager实例，以确保对特定 VISA 库的资源管理是统一的。
        """
        self._rm = visa.ResourceManager()
        """
            self._inst: Resource
        """
        self._inst = None
        self._inst_name: list = []
        self._timeout = 500
        self._name = ''

        # 状态
        self.is_pause = False
        self.is_terminate = False

        # 事件
        self.pause_event = multiprocessing.Event()
        self.stop_event = multiprocessing.Event()

        # 默认的 EOL——终止符
        self.origin_tchar_read = ''
        self.origin_tchar_write = ''

    def __del__(self):
        self._rm.close()

    def init(self) -> list:
        # 打开资源
        self.open_resource()
        if self._inst is None:
            return []
        # 默认设置
        self.default_setup_resource()
        # 返回列表：[“Vendor ID”, “Model Code”, “Serial Number”, “Firmware Revision”]
        return self._inst_name

    def open_resource(self):
        try:
            # 获取资源
            resource = self._rm.open_resource(self._address, open_timeout=3)
            self._inst = resource
            # 返回资源
            return resource
        except Exception as e:
            raise InstrumentConnectionFailed(f'<b>Instrument Connection Failed!</b> ( {self._name} : {self._address})  <p>{str(e)}</p>')

    def default_setup_resource(self):
        """
            终止符
        """
        self.save_origin_termination()
        target_termination = '\n'
        read_termination, write_termination = target_termination, target_termination
        self.set_termination(read_termination, write_termination)
        """
            超时
        """
        self.set_timeout(VISA_TO)

        identify = self.get_identify()
        self.set_inst_name(identify)

    def save_origin_termination(self):
        """
            保存默认的终止符
        """
        self.origin_tchar_read = self._inst.read_termination
        self.origin_tchar_write = self._inst.write_termination

    def set_termination(self, read_termination, write_termination):
        """
            设置终止符
        """
        self._inst.read_termination = read_termination
        self._inst.write_termination = write_termination

    def set_timeout(self, timeout):
        self._inst.timeout = timeout

    def get_identify(self):
        """
            查询仪器的标识信息
            “*” 表示这是一个通用命令，“IDN” 是 “Identify” 的缩写，“?” 表示该命令是一个查询指令
        """
        identify = self.query_cmd("*IDN?")
        self._name = identify
        return identify

    def set_inst_name(self, identify: str):
        if identify:
            # 仪器标识信息
            # split, 以逗号分隔
            values = identify.split(',')
            # 去除左右两边的空格
            values = [value.strip() for value in values]
            self._inst_name = values
        else:
            self._inst_name = []
    def set_pause_event(self, event):
        self.pause_event = event
        pass

    def set_stop_event(self, event):
        self.stop_event = event
        pass

    def pause(self):
        self.is_pause = True

    def go_on(self):
        self.is_pause = False

    def stop(self):
        self.is_pause = False
        self.is_terminate = True

    @property
    def address(self):
        return self._address

    @address.setter
    def address(self, value):
        if not isinstance(value, str):
            raise TypeError('Expected a string')
        self._address = value

    @address.deleter
    def address(self):
        raise AttributeError("Can't delete attribute")

    @property
    def timeout(self):
        return self._inst.timeout

    @timeout.setter
    def timeout(self, value):
        if not isinstance(value, int):
            raise TypeError('Expected a string')
        self._inst.timeout = value

    @timeout.deleter
    def timeout(self):
        raise AttributeError("Can't delete attribute")

    @property
    def name(self):
        return self._name

    @name.setter
    def name(self, value):
        if not isinstance(value, str):
            raise TypeError('Expected a string')
        self._name = value

    def check_stop(self):
        """
            等待
        """
        while self.pause_event.is_set():
            time.sleep(1)
        if self.stop_event.is_set():
            raise Exception('stop task!')

    def init_without_query(self):
        """
            CMW500 在使用
        """
        try:
            self._inst = self._rm.open_resource(self._address)
            if self._inst is None:
                raise Exception('open_resource of instrument failed')
            self._inst.timeout = VISA_TO
        except Exception as e:
            instrument_logger.info(e)
            return ''

    def write(self, cmd: str):
        self.check_stop()
        instrument_logger.info('WCS->{0}: {1}'.format(self.name, cmd))
        self._inst.write(cmd)
        self.scpi_emitted.emit(cmd)
        return True

    def write_cmd(self, cmd: str):
        return self.write(cmd)

    def show_answer(self, value):
        if type(value) is str:
            if len(value) < 1000:
                self.received_answer.emit(value)
            else:
                self.received_answer.emit(value[0:100] + '...')

    def _query(self, cmd: str):
        try:
            ret = self._inst.query(cmd)
            return ret
        # InvalidSession
        except pyvisa.errors.InvalidSession as e:
            logging.error(f"查询命令 '{cmd}' 时发生错误: {e}")
            # 尝试重新打开资源
            self._reopen_resource()
            try:
                ret = self._inst.query(cmd, timeout=3)
                return ret
            except pyvisa.errors.VisaIOError as e2:
                logging.error(f"重新打开资源后查询命令 '{cmd}' 仍发生错误: {e2}")
                return None
        except Exception as e:
            logging.error(f"查询命令 '{cmd}' 时发生错误: {e}")
            # 尝试重新打开资源
            self._reopen_resource()
            try:
                ret = self._inst.query(cmd, timeout=3)
                return ret
            except pyvisa.errors.VisaIOError as e2:
                logging.error(f"重新打开资源后查询命令 '{cmd}' 仍发生错误: {e2}")
                return None

    def _reopen_resource(self):
        try:
            self.set_timeout(5)
            self._inst.close()
            self.set_timeout(VISA_TO)
        except Exception as e:
            logging.error(f"关闭资源时发生错误: {e}")

        try:
            self._inst = self._rm.open_resource(self._address, open_timeout=3)  # 替换为实际资源地址
        except pyvisa.errors.VisaIOError as e:
            logging.error(f"重新打开资源时发生错误: {e}")
            raise

    def query(self, cmd: str, is_show: bool = True):
        self.check_stop()
        # 发送信号出去，把查询指令打印在主界面上。
        if is_show:
            self.scpi_emitted.emit(cmd)
            instrument_logger.info('WCS->{0}: {1}'.format(self.name, cmd))
        # 查询
        ret = self._query(cmd)
        # 打印回复内容
        if is_show:
            self.show_answer(ret)
        instrument_logger.info('WCS<-{0}: {1}'.format(self.name, ret))
        return ret

    def query_cmd(self, cmd: str, is_show: bool = True):
        return self.query(cmd, is_show)

    def read_cmd(self):
        try:
            # 把log 显示在暂停之前， 这样的好处是在暂停的时候刚刚好能看到将要执行那一条指令
            if self.name == 'TA':
                pass
            self.check_stop()
            # CEventHandle.event_wait()
            # self._inst.read_termination = '\n'
            # self._inst.write_termination = '\n'
            ret = self._inst.read()
            instrument_logger.info('WCS<-{0}: {1}'.format(self.name, ret))
            return ret
        except Exception as e:
            if str(e) == 'stop task!':
                raise
            instrument_logger.info(e)
            if 'VI_ERROR_CONN_LOST' in e:
                raise Exception('Connection stopped')
            return ""

    def get_instrument(self, address: str):
        return self._rm.get_instrument(address)

    def get_rm(self):
        return self._rm

    def close(self):
        try:
            # 有些仪器仪表，需要释放后才能再次连接上。
            self._inst.close()
        except Exception as e:
            instrument_logger.info(e)

    def read_raw(self):
        return self._inst._read_raw()
